/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.SpriteCanvasMaterial = function (parameters) {

	THREE.Material.call(this);

	this.type = 'SpriteCanvasMaterial';

	this.color = new THREE.Color(0xffffff);
	this.program = function () { };

	this.setValues(parameters);

};

THREE.SpriteCanvasMaterial.prototype = Object.create(THREE.Material.prototype);
THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial;
THREE.SpriteCanvasMaterial.prototype.isSpriteCanvasMaterial = true;

THREE.SpriteCanvasMaterial.prototype.clone = function () {

	var material = new THREE.SpriteCanvasMaterial();

	material.copy(this);
	material.color.copy(this.color);
	material.program = this.program;

	return material;

};

//



THREE.CanvasRenderer = function (parameters) {

	console.log('THREE.CanvasRenderer', THREE.REVISION);

	parameters = parameters || {};

	var _this = this,
		_renderData, _elements, _lights,
		_projector = new THREE.Projector(),

		_canvas = parameters.canvas !== undefined
			? parameters.canvas
			: document.createElement('canvas'),

		_canvasWidth = _canvas.width,
		_canvasHeight = _canvas.height,
		_canvasWidthHalf = Math.floor(_canvasWidth / 2),
		_canvasHeightHalf = Math.floor(_canvasHeight / 2),

		_viewportX = 0,
		_viewportY = 0,
		_viewportWidth = _canvasWidth,
		_viewportHeight = _canvasHeight,

		_pixelRatio = 1,

		_context = _canvas.getContext('2d', {
			alpha: parameters.alpha === true
		}),

		_clearColor = new THREE.Color(0xff0000),
		_clearAlpha = 1,

		_contextGlobalAlpha = 1,
		_contextGlobalCompositeOperation = 0,
		_contextStrokeStyle = null,
		_contextFillStyle = null,
		_contextLineWidth = null,
		_contextLineCap = null,
		_contextLineJoin = null,
		_contextLineDash = [],

		_v1, _v2, _v3,

		_v1x, _v1y, _v2x, _v2y, _v3x, _v3y,

		_color = new THREE.Color(),

		_diffuseColor = new THREE.Color(),
		_emissiveColor = new THREE.Color(),

		_lightColor = new THREE.Color(),

		_patterns = {},

		_uvs,
		_uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,

		_clipBox = new THREE.Box2(),
		_clearBox = new THREE.Box2(),
		_elemBox = new THREE.Box2(),

		_ambientLight = new THREE.Color(),
		_directionalLights = new THREE.Color(),
		_pointLights = new THREE.Color(),

		_vector3 = new THREE.Vector3(), // Needed for PointLight
		_centroid = new THREE.Vector3(),
		_normal = new THREE.Vector3(),
		_normalViewMatrix = new THREE.Matrix3();

	/* TODO
	_canvas.mozImageSmoothingEnabled = false;
	_canvas.webkitImageSmoothingEnabled = false;
	_canvas.msImageSmoothingEnabled = false;
	_canvas.imageSmoothingEnabled = false;
	*/

	// dash+gap fallbacks for Firefox and everything else

	if (_context.setLineDash === undefined) {

		_context.setLineDash = function () { };

	}

	this.domElement = _canvas;

	this.autoClear = true;
	this.sortObjects = true;
	this.sortElements = true;

	this.info = {

		render: {

			vertices: 0,
			faces: 0

		}

	};

	// WebGLRenderer compatibility

	this.supportsVertexTextures = function () { };
	this.setFaceCulling = function () { };

	// API

	this.getContext = function () {

		return _context;

	};

	this.getContextAttributes = function () {

		return _context.getContextAttributes();

	};

	this.getPixelRatio = function () {

		return _pixelRatio;

	};

	this.setPixelRatio = function (value) {

		if (value !== undefined) _pixelRatio = value;

	};

	this.setSize = function (width, height, updateStyle) {

		_canvasWidth = width * _pixelRatio;
		_canvasHeight = height * _pixelRatio;

		_canvas.width = _canvasWidth;
		_canvas.height = _canvasHeight;

		_canvasWidthHalf = Math.floor(_canvasWidth / 2);
		_canvasHeightHalf = Math.floor(_canvasHeight / 2);

		if (updateStyle !== false) {

			_canvas.style.width = width + 'px';
			_canvas.style.height = height + 'px';

		}

		_clipBox.min.set(- _canvasWidthHalf, - _canvasHeightHalf);
		_clipBox.max.set(_canvasWidthHalf, _canvasHeightHalf);

		_clearBox.min.set(- _canvasWidthHalf, - _canvasHeightHalf);
		_clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf);

		_contextGlobalAlpha = 1;
		_contextGlobalCompositeOperation = 0;
		_contextStrokeStyle = null;
		_contextFillStyle = null;
		_contextLineWidth = null;
		_contextLineCap = null;
		_contextLineJoin = null;

		this.setViewport(0, 0, width, height);

	};

	this.setViewport = function (x, y, width, height) {

		_viewportX = x * _pixelRatio;
		_viewportY = y * _pixelRatio;

		_viewportWidth = width * _pixelRatio;
		_viewportHeight = height * _pixelRatio;

	};

	this.setScissor = function () { };
	this.setScissorTest = function () { };

	this.setClearColor = function (color, alpha) {

		_clearColor.set(color);
		_clearAlpha = alpha !== undefined ? alpha : 1;

		_clearBox.min.set(- _canvasWidthHalf, - _canvasHeightHalf);
		_clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf);

	};

	this.setClearColorHex = function (hex, alpha) {

		console.warn('THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.');
		this.setClearColor(hex, alpha);

	};

	this.getClearColor = function () {

		return _clearColor;

	};

	this.getClearAlpha = function () {

		return _clearAlpha;

	};

	this.getMaxAnisotropy = function () {

		return 0;

	};

	this.clear = function () {

		if (_clearBox.isEmpty() === false) {

			_clearBox.intersect(_clipBox);
			_clearBox.expandByScalar(2);

			_clearBox.min.x = _clearBox.min.x + _canvasWidthHalf;
			_clearBox.min.y = - _clearBox.min.y + _canvasHeightHalf;		// higher y value !
			_clearBox.max.x = _clearBox.max.x + _canvasWidthHalf;
			_clearBox.max.y = - _clearBox.max.y + _canvasHeightHalf;		// lower y value !

			if (_clearAlpha < 1) {

				_context.clearRect(
					_clearBox.min.x | 0,
					_clearBox.max.y | 0,
					(_clearBox.max.x - _clearBox.min.x) | 0,
					(_clearBox.min.y - _clearBox.max.y) | 0
				);

			}

			if (_clearAlpha > 0) {

				setBlending(THREE.NormalBlending);
				setOpacity(1);

				setFillStyle('rgba(' + Math.floor(_clearColor.r * 255) + ',' + Math.floor(_clearColor.g * 255) + ',' + Math.floor(_clearColor.b * 255) + ',' + _clearAlpha + ')');

				_context.fillRect(
					_clearBox.min.x | 0,
					_clearBox.max.y | 0,
					(_clearBox.max.x - _clearBox.min.x) | 0,
					(_clearBox.min.y - _clearBox.max.y) | 0
				);

			}

			_clearBox.makeEmpty();

		}

	};

	// compatibility

	this.clearColor = function () { };
	this.clearDepth = function () { };
	this.clearStencil = function () { };

	this.render = function (scene, camera) {

		if (camera.isCamera === undefined) {

			console.error('THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.');
			return;

		}

		var background = scene.background;

		//scene.background = new THREE.Color( 0xff0000 );

		if (background && background.isColor) {

			setFillStyle('rgb(0,0,0)');
			_context.fillRect(0, 0, _canvasWidth, _canvasHeight);

		} else if (this.autoClear === true) {

			this.clear();

		}

		_this.info.render.vertices = 0;
		_this.info.render.faces = 0;

		_context.setTransform(_viewportWidth / _canvasWidth, 0, 0, - _viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY);
		_context.translate(_canvasWidthHalf, _canvasHeightHalf);

		_renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements);
		_elements = _renderData.elements;
		_lights = _renderData.lights;

		_normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse);

		/* DEBUG
		setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
		_context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
		*/

		calculateLights();

		for (var e = 0, el = _elements.length; e < el; e++) {

			var element = _elements[e];

			var material = element.material;

			if (material === undefined || material.opacity === 0) continue;

			_elemBox.makeEmpty();

			if (element instanceof THREE.RenderableSprite) {

				_v1 = element;
				_v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;

				renderSprite(_v1, element, material);

			} else if (element instanceof THREE.RenderableLine) {

				_v1 = element.v1; _v2 = element.v2;

				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;

				_elemBox.setFromPoints([
					_v1.positionScreen,
					_v2.positionScreen
				]);

				if (_clipBox.intersectsBox(_elemBox) === true) {

					renderLine(_v1, _v2, element, material);

				}

			} else if (element instanceof THREE.RenderableFace) {

				_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;

				if (_v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1) continue;
				if (_v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1) continue;
				if (_v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1) continue;

				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
				_v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;

				if (material.overdraw > 0) {

					expand(_v1.positionScreen, _v2.positionScreen, material.overdraw);
					expand(_v2.positionScreen, _v3.positionScreen, material.overdraw);
					expand(_v3.positionScreen, _v1.positionScreen, material.overdraw);

				}

				_elemBox.setFromPoints([
					_v1.positionScreen,
					_v2.positionScreen,
					_v3.positionScreen
				]);

				if (_clipBox.intersectsBox(_elemBox) === true) {

					renderFace3(_v1, _v2, _v3, 0, 1, 2, element, material);

				}

			}

			/* DEBUG
			setLineWidth( 1 );
			setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
			_context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
			*/

			_clearBox.union(_elemBox);

		}

		/* DEBUG
		setLineWidth( 1 );
		setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
		_context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
		*/

		_context.setTransform(1, 0, 0, 1, 0, 0);

	};

	//

	function calculateLights() {

		_ambientLight.setRGB(0, 0, 0);
		_directionalLights.setRGB(0, 0, 0);
		_pointLights.setRGB(0, 0, 0);

		for (var l = 0, ll = _lights.length; l < ll; l++) {

			var light = _lights[l];
			var lightColor = light.color;

			if (light.isAmbientLight) {

				_ambientLight.add(lightColor);

			} else if (light.isDirectionalLight) {

				// for sprites

				_directionalLights.add(lightColor);

			} else if (light.isPointLight) {

				// for sprites

				_pointLights.add(lightColor);

			}

		}

	}

	function calculateLight(position, normal, color) {

		for (var l = 0, ll = _lights.length; l < ll; l++) {

			var light = _lights[l];

			_lightColor.copy(light.color);

			if (light.isDirectionalLight) {

				var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).normalize();

				var amount = normal.dot(lightPosition);

				if (amount <= 0) continue;

				amount *= light.intensity;

				color.add(_lightColor.multiplyScalar(amount));

			} else if (light.isPointLight) {

				var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld);

				var amount = normal.dot(_vector3.subVectors(lightPosition, position).normalize());

				if (amount <= 0) continue;

				amount *= light.distance == 0 ? 1 : 1 - Math.min(position.distanceTo(lightPosition) / light.distance, 1);

				if (amount == 0) continue;

				amount *= light.intensity;

				color.add(_lightColor.multiplyScalar(amount));

			}

		}

	}

	function renderSprite(v1, element, material) {

		setOpacity(material.opacity);
		setBlending(material.blending);

		var scaleX = element.scale.x * _canvasWidthHalf;
		var scaleY = element.scale.y * _canvasHeightHalf;

		var dist = Math.sqrt(scaleX * scaleX + scaleY * scaleY); // allow for rotated sprite
		_elemBox.min.set(v1.x - dist, v1.y - dist);
		_elemBox.max.set(v1.x + dist, v1.y + dist);

		if (material.isSpriteMaterial) {

			var texture = material.map;

			if (texture !== null) {

				var pattern = _patterns[texture.id];

				if (pattern === undefined || pattern.version !== texture.version) {

					pattern = textureToPattern(texture);
					_patterns[texture.id] = pattern;

				}

				if (pattern.canvas !== undefined) {

					setFillStyle(pattern.canvas);

					var bitmap = texture.image;

					var ox = bitmap.width * texture.offset.x;
					var oy = bitmap.height * texture.offset.y;

					var sx = bitmap.width * texture.repeat.x;
					var sy = bitmap.height * texture.repeat.y;

					var cx = scaleX / sx;
					var cy = scaleY / sy;

					_context.save();
					_context.translate(v1.x, v1.y);
					if (material.rotation !== 0) _context.rotate(material.rotation);
					_context.translate(- scaleX / 2, - scaleY / 2);
					_context.scale(cx, cy);
					_context.translate(- ox, - oy);
					_context.fillRect(ox, oy, sx, sy);
					_context.restore();

				}

			} else {

				// no texture

				setFillStyle(material.color.getStyle());

				_context.save();
				_context.translate(v1.x, v1.y);
				if (material.rotation !== 0) _context.rotate(material.rotation);
				_context.scale(scaleX, - scaleY);
				_context.fillRect(- 0.5, - 0.5, 1, 1);
				_context.restore();

			}

		} else if (material.isSpriteCanvasMaterial) {

			setStrokeStyle(material.color.getStyle());
			setFillStyle(material.color.getStyle());

			_context.save();
			_context.translate(v1.x, v1.y);
			if (material.rotation !== 0) _context.rotate(material.rotation);
			_context.scale(scaleX, scaleY);

			material.program(_context);

			_context.restore();

		} else if (material.isPointsMaterial) {

			setFillStyle(material.color.getStyle());

			_context.save();
			_context.translate(v1.x, v1.y);
			if (material.rotation !== 0) _context.rotate(material.rotation);
			_context.scale(scaleX * material.size, - scaleY * material.size);
			_context.fillRect(- 0.5, - 0.5, 1, 1);
			_context.restore();

		}

		/* DEBUG
		setStrokeStyle( 'rgb(255,255,0)' );
		_context.beginPath();
		_context.moveTo( v1.x - 10, v1.y );
		_context.lineTo( v1.x + 10, v1.y );
		_context.moveTo( v1.x, v1.y - 10 );
		_context.lineTo( v1.x, v1.y + 10 );
		_context.stroke();
		*/

	}

	function renderLine(v1, v2, element, material) {

		setOpacity(material.opacity);
		setBlending(material.blending);

		_context.beginPath();
		_context.moveTo(v1.positionScreen.x, v1.positionScreen.y);
		_context.lineTo(v2.positionScreen.x, v2.positionScreen.y);

		if (material.isLineBasicMaterial) {

			setLineWidth(material.linewidth);
			setLineCap(material.linecap);
			setLineJoin(material.linejoin);

			if (material.vertexColors !== THREE.VertexColors) {

				setStrokeStyle(material.color.getStyle());

			} else {

				var colorStyle1 = element.vertexColors[0].getStyle();
				var colorStyle2 = element.vertexColors[1].getStyle();

				if (colorStyle1 === colorStyle2) {

					setStrokeStyle(colorStyle1);

				} else {

					try {

						var grad = _context.createLinearGradient(
							v1.positionScreen.x,
							v1.positionScreen.y,
							v2.positionScreen.x,
							v2.positionScreen.y
						);
						grad.addColorStop(0, colorStyle1);
						grad.addColorStop(1, colorStyle2);

					} catch (exception) {

						grad = colorStyle1;

					}

					setStrokeStyle(grad);

				}

			}

			_context.stroke();
			_elemBox.expandByScalar(material.linewidth * 2);

		} else if (material.isLineDashedMaterial) {

			setLineWidth(material.linewidth);
			setLineCap(material.linecap);
			setLineJoin(material.linejoin);
			setStrokeStyle(material.color.getStyle());
			setLineDash([material.dashSize, material.gapSize]);

			_context.stroke();

			_elemBox.expandByScalar(material.linewidth * 2);

			setLineDash([]);

		}

	}

	function renderFace3(v1, v2, v3, uv1, uv2, uv3, element, material) {

		_this.info.render.vertices += 3;
		_this.info.render.faces++;

		setOpacity(material.opacity);
		setBlending(material.blending);

		_v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
		_v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
		_v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;

		drawTriangle(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y);

		if ((material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) && material.map === null) {

			_diffuseColor.copy(material.color);
			_emissiveColor.copy(material.emissive);

			if (material.vertexColors === THREE.FaceColors) {

				_diffuseColor.multiply(element.color);

			}

			_color.copy(_ambientLight);

			_centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3);

			calculateLight(_centroid, element.normalModel, _color);

			_color.multiply(_diffuseColor).add(_emissiveColor);

			material.wireframe === true
				? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
				: fillPath(_color);

		} else if (material.isMeshBasicMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) {

			if (material.map !== null) {

				var mapping = material.map.mapping;

				if (mapping === THREE.UVMapping) {

					_uvs = element.uvs;
					patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[uv1].x, _uvs[uv1].y, _uvs[uv2].x, _uvs[uv2].y, _uvs[uv3].x, _uvs[uv3].y, material.map);

				}

			} else if (material.envMap !== null) {

				if (material.envMap.mapping === THREE.SphericalReflectionMapping) {

					_normal.copy(element.vertexNormalsModel[uv1]).applyMatrix3(_normalViewMatrix);
					_uv1x = 0.5 * _normal.x + 0.5;
					_uv1y = 0.5 * _normal.y + 0.5;

					_normal.copy(element.vertexNormalsModel[uv2]).applyMatrix3(_normalViewMatrix);
					_uv2x = 0.5 * _normal.x + 0.5;
					_uv2y = 0.5 * _normal.y + 0.5;

					_normal.copy(element.vertexNormalsModel[uv3]).applyMatrix3(_normalViewMatrix);
					_uv3x = 0.5 * _normal.x + 0.5;
					_uv3y = 0.5 * _normal.y + 0.5;

					patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap);

				}

			} else {

				_color.copy(material.color);

				if (material.vertexColors === THREE.FaceColors) {

					_color.multiply(element.color);

				}

				material.wireframe === true
					? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
					: fillPath(_color);

			}

		} else if (material.isMeshNormalMaterial) {

			_normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix);

			_color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5);

			material.wireframe === true
				? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
				: fillPath(_color);

		} else {

			_color.setRGB(1, 1, 1);

			material.wireframe === true
				? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
				: fillPath(_color);

		}

	}

	//

	function drawTriangle(x0, y0, x1, y1, x2, y2) {

		_context.beginPath();
		_context.moveTo(x0, y0);
		_context.lineTo(x1, y1);
		_context.lineTo(x2, y2);
		_context.closePath();

	}

	function strokePath(color, linewidth, linecap, linejoin) {

		setLineWidth(linewidth);
		setLineCap(linecap);
		setLineJoin(linejoin);
		setStrokeStyle(color.getStyle());

		_context.stroke();

		_elemBox.expandByScalar(linewidth * 2);

	}

	function fillPath(color) {

		setFillStyle(color.getStyle());
		_context.fill();

	}

	function textureToPattern(texture) {

		if (texture.version === 0 ||
			texture instanceof THREE.CompressedTexture ||
			texture instanceof THREE.DataTexture) {

			return {
				canvas: undefined,
				version: texture.version
			};

		}

		var image = texture.image;

		if (image.complete === false) {

			return {
				canvas: undefined,
				version: 0
			};

		}

		var repeatX = texture.wrapS === THREE.RepeatWrapping || texture.wrapS === THREE.MirroredRepeatWrapping;
		var repeatY = texture.wrapT === THREE.RepeatWrapping || texture.wrapT === THREE.MirroredRepeatWrapping;

		var mirrorX = texture.wrapS === THREE.MirroredRepeatWrapping;
		var mirrorY = texture.wrapT === THREE.MirroredRepeatWrapping;

		//

		var canvas = document.createElement('canvas');
		canvas.width = image.width * (mirrorX ? 2 : 1);
		canvas.height = image.height * (mirrorY ? 2 : 1);

		var context = canvas.getContext('2d');
		context.setTransform(1, 0, 0, - 1, 0, image.height);
		context.drawImage(image, 0, 0);

		if (mirrorX === true) {

			context.setTransform(- 1, 0, 0, - 1, image.width, image.height);
			context.drawImage(image, - image.width, 0);

		}

		if (mirrorY === true) {

			context.setTransform(1, 0, 0, 1, 0, 0);
			context.drawImage(image, 0, image.height);

		}

		if (mirrorX === true && mirrorY === true) {

			context.setTransform(- 1, 0, 0, 1, image.width, 0);
			context.drawImage(image, - image.width, image.height);

		}

		var repeat = 'no-repeat';

		if (repeatX === true && repeatY === true) {

			repeat = 'repeat';

		} else if (repeatX === true) {

			repeat = 'repeat-x';

		} else if (repeatY === true) {

			repeat = 'repeat-y';

		}

		var pattern = _context.createPattern(canvas, repeat);

		if (texture.onUpdate) texture.onUpdate(texture);

		return {
			canvas: pattern,
			version: texture.version
		};

	}

	function patternPath(x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture) {

		var pattern = _patterns[texture.id];

		if (pattern === undefined || pattern.version !== texture.version) {

			pattern = textureToPattern(texture);
			_patterns[texture.id] = pattern;

		}

		if (pattern.canvas !== undefined) {

			setFillStyle(pattern.canvas);

		} else {

			setFillStyle('rgba( 0, 0, 0, 1)');
			_context.fill();
			return;

		}

		// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

		var a, b, c, d, e, f, det, idet,
			offsetX = texture.offset.x / texture.repeat.x,
			offsetY = texture.offset.y / texture.repeat.y,
			width = texture.image.width * texture.repeat.x,
			height = texture.image.height * texture.repeat.y;

		u0 = (u0 + offsetX) * width;
		v0 = (v0 + offsetY) * height;

		u1 = (u1 + offsetX) * width;
		v1 = (v1 + offsetY) * height;

		u2 = (u2 + offsetX) * width;
		v2 = (v2 + offsetY) * height;

		x1 -= x0; y1 -= y0;
		x2 -= x0; y2 -= y0;

		u1 -= u0; v1 -= v0;
		u2 -= u0; v2 -= v0;

		det = u1 * v2 - u2 * v1;

		if (det === 0) return;

		idet = 1 / det;

		a = (v2 * x1 - v1 * x2) * idet;
		b = (v2 * y1 - v1 * y2) * idet;
		c = (u1 * x2 - u2 * x1) * idet;
		d = (u1 * y2 - u2 * y1) * idet;

		e = x0 - a * u0 - c * v0;
		f = y0 - b * u0 - d * v0;

		_context.save();
		_context.transform(a, b, c, d, e, f);
		_context.fill();
		_context.restore();

	}

	/*
	function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {

	  // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

	  var a, b, c, d, e, f, det, idet,
	  width = image.width - 1,
	  height = image.height - 1;

	  u0 *= width; v0 *= height;
	  u1 *= width; v1 *= height;
	  u2 *= width; v2 *= height;

	  x1 -= x0; y1 -= y0;
	  x2 -= x0; y2 -= y0;

	  u1 -= u0; v1 -= v0;
	  u2 -= u0; v2 -= v0;

	  det = u1 * v2 - u2 * v1;

	  idet = 1 / det;

	  a = ( v2 * x1 - v1 * x2 ) * idet;
	  b = ( v2 * y1 - v1 * y2 ) * idet;
	  c = ( u1 * x2 - u2 * x1 ) * idet;
	  d = ( u1 * y2 - u2 * y1 ) * idet;

	  e = x0 - a * u0 - c * v0;
	  f = y0 - b * u0 - d * v0;

	  _context.save();
	  _context.transform( a, b, c, d, e, f );
	  _context.clip();
	  _context.drawImage( image, 0, 0 );
	  _context.restore();

	}
	*/

	// Hide anti-alias gaps

	function expand(v1, v2, pixels) {

		var x = v2.x - v1.x, y = v2.y - v1.y,
			det = x * x + y * y, idet;

		if (det === 0) return;

		idet = pixels / Math.sqrt(det);

		x *= idet; y *= idet;

		v2.x += x; v2.y += y;
		v1.x -= x; v1.y -= y;

	}

	// Context cached methods.

	function setOpacity(value) {

		if (_contextGlobalAlpha !== value) {

			_context.globalAlpha = value;
			_contextGlobalAlpha = value;

		}

	}

	function setBlending(value) {

		if (_contextGlobalCompositeOperation !== value) {

			if (value === THREE.NormalBlending) {

				_context.globalCompositeOperation = 'source-over';

			} else if (value === THREE.AdditiveBlending) {

				_context.globalCompositeOperation = 'lighter';

			} else if (value === THREE.SubtractiveBlending) {

				_context.globalCompositeOperation = 'darker';

			} else if (value === THREE.MultiplyBlending) {

				_context.globalCompositeOperation = 'multiply';

			}

			_contextGlobalCompositeOperation = value;

		}

	}

	function setLineWidth(value) {

		if (_contextLineWidth !== value) {

			_context.lineWidth = value;
			_contextLineWidth = value;

		}

	}

	function setLineCap(value) {

		// "butt", "round", "square"

		if (_contextLineCap !== value) {

			_context.lineCap = value;
			_contextLineCap = value;

		}

	}

	function setLineJoin(value) {

		// "round", "bevel", "miter"

		if (_contextLineJoin !== value) {

			_context.lineJoin = value;
			_contextLineJoin = value;

		}

	}

	function setStrokeStyle(value) {

		if (_contextStrokeStyle !== value) {

			_context.strokeStyle = value;
			_contextStrokeStyle = value;

		}

	}

	function setFillStyle(value) {

		if (_contextFillStyle !== value) {

			_context.fillStyle = value;
			_contextFillStyle = value;

		}

	}

	function setLineDash(value) {

		if (_contextLineDash.length !== value.length) {

			_context.setLineDash(value);
			_contextLineDash = value;

		}

	}

};
















/**
 * @author mrdoob / http://mrdoob.com/
 * @author supereggbert / http://www.paulbrunt.co.uk/
 * @author julianwa / https://github.com/julianwa
 */

THREE.RenderableObject = function () {

	this.id = 0;

	this.object = null;
	this.z = 0;
	this.renderOrder = 0;

};

//

THREE.RenderableFace = function () {

	this.id = 0;

	this.v1 = new THREE.RenderableVertex();
	this.v2 = new THREE.RenderableVertex();
	this.v3 = new THREE.RenderableVertex();

	this.normalModel = new THREE.Vector3();

	this.vertexNormalsModel = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
	this.vertexNormalsLength = 0;

	this.color = new THREE.Color();
	this.material = null;
	this.uvs = [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()];

	this.z = 0;
	this.renderOrder = 0;

};

//

THREE.RenderableVertex = function () {

	this.position = new THREE.Vector3();
	this.positionWorld = new THREE.Vector3();
	this.positionScreen = new THREE.Vector4();

	this.visible = true;

};

THREE.RenderableVertex.prototype.copy = function (vertex) {

	this.positionWorld.copy(vertex.positionWorld);
	this.positionScreen.copy(vertex.positionScreen);

};

//

THREE.RenderableLine = function () {

	this.id = 0;

	this.v1 = new THREE.RenderableVertex();
	this.v2 = new THREE.RenderableVertex();

	this.vertexColors = [new THREE.Color(), new THREE.Color()];
	this.material = null;

	this.z = 0;
	this.renderOrder = 0;

};

//

THREE.RenderableSprite = function () {

	this.id = 0;

	this.object = null;

	this.x = 0;
	this.y = 0;
	this.z = 0;

	this.rotation = 0;
	this.scale = new THREE.Vector2();

	this.material = null;
	this.renderOrder = 0;

};

//

THREE.Projector = function () {

	var _object, _objectCount, _objectPool = [], _objectPoolLength = 0,
		_vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0,
		_face, _faceCount, _facePool = [], _facePoolLength = 0,
		_line, _lineCount, _linePool = [], _linePoolLength = 0,
		_sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0,

		_renderData = { objects: [], lights: [], elements: [] },

		_vector3 = new THREE.Vector3(),
		_vector4 = new THREE.Vector4(),

		_clipBox = new THREE.Box3(new THREE.Vector3(- 1, - 1, - 1), new THREE.Vector3(1, 1, 1)),
		_boundingBox = new THREE.Box3(),
		_points3 = new Array(3),

		_viewMatrix = new THREE.Matrix4(),
		_viewProjectionMatrix = new THREE.Matrix4(),

		_modelMatrix,
		_modelViewProjectionMatrix = new THREE.Matrix4(),

		_normalMatrix = new THREE.Matrix3(),

		_frustum = new THREE.Frustum(),

		_clippedVertex1PositionScreen = new THREE.Vector4(),
		_clippedVertex2PositionScreen = new THREE.Vector4();

	//

	this.projectVector = function (vector, camera) {

		console.warn('THREE.Projector: .projectVector() is now vector.project().');
		vector.project(camera);

	};

	this.unprojectVector = function (vector, camera) {

		console.warn('THREE.Projector: .unprojectVector() is now vector.unproject().');
		vector.unproject(camera);

	};

	this.pickingRay = function () {

		console.error('THREE.Projector: .pickingRay() is now raycaster.setFromCamera().');

	};

	//

	var RenderList = function () {

		var normals = [];
		var colors = [];
		var uvs = [];

		var object = null;
		var material = null;

		var normalMatrix = new THREE.Matrix3();

		function setObject(value) {

			object = value;
			material = object.material;

			normalMatrix.getNormalMatrix(object.matrixWorld);

			normals.length = 0;
			colors.length = 0;
			uvs.length = 0;

		}

		function projectVertex(vertex) {

			var position = vertex.position;
			var positionWorld = vertex.positionWorld;
			var positionScreen = vertex.positionScreen;

			positionWorld.copy(position).applyMatrix4(_modelMatrix);
			positionScreen.copy(positionWorld).applyMatrix4(_viewProjectionMatrix);

			var invW = 1 / positionScreen.w;

			positionScreen.x *= invW;
			positionScreen.y *= invW;
			positionScreen.z *= invW;

			vertex.visible = positionScreen.x >= - 1 && positionScreen.x <= 1 &&
				positionScreen.y >= - 1 && positionScreen.y <= 1 &&
				positionScreen.z >= - 1 && positionScreen.z <= 1;

		}

		function pushVertex(x, y, z) {

			_vertex = getNextVertexInPool();
			_vertex.position.set(x, y, z);

			projectVertex(_vertex);

		}

		function pushNormal(x, y, z) {

			normals.push(x, y, z);

		}

		function pushColor(r, g, b) {

			colors.push(r, g, b);

		}

		function pushUv(x, y) {

			uvs.push(x, y);

		}

		function checkTriangleVisibility(v1, v2, v3) {

			if (v1.visible === true || v2.visible === true || v3.visible === true) return true;

			_points3[0] = v1.positionScreen;
			_points3[1] = v2.positionScreen;
			_points3[2] = v3.positionScreen;

			return _clipBox.intersectsBox(_boundingBox.setFromPoints(_points3));

		}

		function checkBackfaceCulling(v1, v2, v3) {

			return ((v3.positionScreen.x - v1.positionScreen.x) *
				(v2.positionScreen.y - v1.positionScreen.y) -
				(v3.positionScreen.y - v1.positionScreen.y) *
				(v2.positionScreen.x - v1.positionScreen.x)) < 0;

		}

		function pushLine(a, b) {

			var v1 = _vertexPool[a];
			var v2 = _vertexPool[b];

			// Clip

			v1.positionScreen.copy(v1.position).applyMatrix4(_modelViewProjectionMatrix);
			v2.positionScreen.copy(v2.position).applyMatrix4(_modelViewProjectionMatrix);

			if (clipLine(v1.positionScreen, v2.positionScreen) === true) {

				// Perform the perspective divide
				v1.positionScreen.multiplyScalar(1 / v1.positionScreen.w);
				v2.positionScreen.multiplyScalar(1 / v2.positionScreen.w);

				_line = getNextLineInPool();
				_line.id = object.id;
				_line.v1.copy(v1);
				_line.v2.copy(v2);
				_line.z = Math.max(v1.positionScreen.z, v2.positionScreen.z);
				_line.renderOrder = object.renderOrder;

				_line.material = object.material;

				if (object.material.vertexColors === THREE.VertexColors) {

					_line.vertexColors[0].fromArray(colors, a * 3);
					_line.vertexColors[1].fromArray(colors, b * 3);

				}

				_renderData.elements.push(_line);

			}

		}

		function pushTriangle(a, b, c) {

			var v1 = _vertexPool[a];
			var v2 = _vertexPool[b];
			var v3 = _vertexPool[c];

			if (checkTriangleVisibility(v1, v2, v3) === false) return;

			if (material.side === THREE.DoubleSide || checkBackfaceCulling(v1, v2, v3) === true) {

				_face = getNextFaceInPool();

				_face.id = object.id;
				_face.v1.copy(v1);
				_face.v2.copy(v2);
				_face.v3.copy(v3);
				_face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3;
				_face.renderOrder = object.renderOrder;

				// use first vertex normal as face normal

				_face.normalModel.fromArray(normals, a * 3);
				_face.normalModel.applyMatrix3(normalMatrix).normalize();

				for (var i = 0; i < 3; i++) {

					var normal = _face.vertexNormalsModel[i];
					normal.fromArray(normals, arguments[i] * 3);
					normal.applyMatrix3(normalMatrix).normalize();

					var uv = _face.uvs[i];
					uv.fromArray(uvs, arguments[i] * 2);

				}

				_face.vertexNormalsLength = 3;

				_face.material = object.material;

				_renderData.elements.push(_face);

			}

		}

		return {
			setObject: setObject,
			projectVertex: projectVertex,
			checkTriangleVisibility: checkTriangleVisibility,
			checkBackfaceCulling: checkBackfaceCulling,
			pushVertex: pushVertex,
			pushNormal: pushNormal,
			pushColor: pushColor,
			pushUv: pushUv,
			pushLine: pushLine,
			pushTriangle: pushTriangle
		};

	};

	var renderList = new RenderList();

	function projectObject(object) {

		if (object.visible === false) return;

		if (object instanceof THREE.Light) {

			_renderData.lights.push(object);

		} else if (object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points) {

			if (object.material.visible === false) return;
			if (object.frustumCulled === true && _frustum.intersectsObject(object) === false) return;

			addObject(object);

		} else if (object instanceof THREE.Sprite) {

			if (object.material.visible === false) return;
			if (object.frustumCulled === true && _frustum.intersectsSprite(object) === false) return;

			addObject(object);

		}

		var children = object.children;

		for (var i = 0, l = children.length; i < l; i++) {

			projectObject(children[i]);

		}

	}

	function addObject(object) {

		_object = getNextObjectInPool();
		_object.id = object.id;
		_object.object = object;

		_vector3.setFromMatrixPosition(object.matrixWorld);
		_vector3.applyMatrix4(_viewProjectionMatrix);
		_object.z = _vector3.z;
		_object.renderOrder = object.renderOrder;

		_renderData.objects.push(_object);

	}

	this.projectScene = function (scene, camera, sortObjects, sortElements) {

		_faceCount = 0;
		_lineCount = 0;
		_spriteCount = 0;

		_renderData.elements.length = 0;

		if (scene.autoUpdate === true) scene.updateMatrixWorld();
		if (camera.parent === null) camera.updateMatrixWorld();

		_viewMatrix.copy(camera.matrixWorldInverse);
		_viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix);

		_frustum.setFromMatrix(_viewProjectionMatrix);

		//

		_objectCount = 0;

		_renderData.objects.length = 0;
		_renderData.lights.length = 0;

		projectObject(scene);

		if (sortObjects === true) {

			_renderData.objects.sort(painterSort);

		}

		//

		var objects = _renderData.objects;

		for (var o = 0, ol = objects.length; o < ol; o++) {

			var object = objects[o].object;
			var geometry = object.geometry;

			renderList.setObject(object);

			_modelMatrix = object.matrixWorld;

			_vertexCount = 0;

			if (object instanceof THREE.Mesh) {

				if (geometry instanceof THREE.BufferGeometry) {

					var attributes = geometry.attributes;
					var groups = geometry.groups;

					if (attributes.position === undefined) continue;

					var positions = attributes.position.array;

					for (var i = 0, l = positions.length; i < l; i += 3) {

						renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]);

					}

					if (attributes.normal !== undefined) {

						var normals = attributes.normal.array;

						for (var i = 0, l = normals.length; i < l; i += 3) {

							renderList.pushNormal(normals[i], normals[i + 1], normals[i + 2]);

						}

					}

					if (attributes.uv !== undefined) {

						var uvs = attributes.uv.array;

						for (var i = 0, l = uvs.length; i < l; i += 2) {

							renderList.pushUv(uvs[i], uvs[i + 1]);

						}

					}

					if (geometry.index !== null) {

						var indices = geometry.index.array;

						if (groups.length > 0) {

							for (var g = 0; g < groups.length; g++) {

								var group = groups[g];

								for (var i = group.start, l = group.start + group.count; i < l; i += 3) {

									renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2]);

								}

							}

						} else {

							for (var i = 0, l = indices.length; i < l; i += 3) {

								renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2]);

							}

						}

					} else {

						for (var i = 0, l = positions.length / 3; i < l; i += 3) {

							renderList.pushTriangle(i, i + 1, i + 2);

						}

					}

				} else if (geometry instanceof THREE.Geometry) {

					var vertices = geometry.vertices;
					var faces = geometry.faces;
					var faceVertexUvs = geometry.faceVertexUvs[0];

					_normalMatrix.getNormalMatrix(_modelMatrix);

					var material = object.material;

					var isMultiMaterial = Array.isArray(material);

					for (var v = 0, vl = vertices.length; v < vl; v++) {

						var vertex = vertices[v];

						_vector3.copy(vertex);

						if (material.morphTargets === true) {

							var morphTargets = geometry.morphTargets;
							var morphInfluences = object.morphTargetInfluences;

							for (var t = 0, tl = morphTargets.length; t < tl; t++) {

								var influence = morphInfluences[t];

								if (influence === 0) continue;

								var target = morphTargets[t];
								var targetVertex = target.vertices[v];

								_vector3.x += (targetVertex.x - vertex.x) * influence;
								_vector3.y += (targetVertex.y - vertex.y) * influence;
								_vector3.z += (targetVertex.z - vertex.z) * influence;

							}

						}

						renderList.pushVertex(_vector3.x, _vector3.y, _vector3.z);

					}

					for (var f = 0, fl = faces.length; f < fl; f++) {

						var face = faces[f];

						material = isMultiMaterial === true
							? object.material[face.materialIndex]
							: object.material;

						if (material === undefined) continue;

						var side = material.side;

						var v1 = _vertexPool[face.a];
						var v2 = _vertexPool[face.b];
						var v3 = _vertexPool[face.c];

						if (renderList.checkTriangleVisibility(v1, v2, v3) === false) continue;

						var visible = renderList.checkBackfaceCulling(v1, v2, v3);

						if (side !== THREE.DoubleSide) {

							if (side === THREE.FrontSide && visible === false) continue;
							if (side === THREE.BackSide && visible === true) continue;

						}

						_face = getNextFaceInPool();

						_face.id = object.id;
						_face.v1.copy(v1);
						_face.v2.copy(v2);
						_face.v3.copy(v3);

						_face.normalModel.copy(face.normal);

						if (visible === false && (side === THREE.BackSide || side === THREE.DoubleSide)) {

							_face.normalModel.negate();

						}

						_face.normalModel.applyMatrix3(_normalMatrix).normalize();

						var faceVertexNormals = face.vertexNormals;

						for (var n = 0, nl = Math.min(faceVertexNormals.length, 3); n < nl; n++) {

							var normalModel = _face.vertexNormalsModel[n];
							normalModel.copy(faceVertexNormals[n]);

							if (visible === false && (side === THREE.BackSide || side === THREE.DoubleSide)) {

								normalModel.negate();

							}

							normalModel.applyMatrix3(_normalMatrix).normalize();

						}

						_face.vertexNormalsLength = faceVertexNormals.length;

						var vertexUvs = faceVertexUvs[f];

						if (vertexUvs !== undefined) {

							for (var u = 0; u < 3; u++) {

								_face.uvs[u].copy(vertexUvs[u]);

							}

						}

						_face.color = face.color;
						_face.material = material;

						_face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3;
						_face.renderOrder = object.renderOrder;

						_renderData.elements.push(_face);

					}

				}

			} else if (object instanceof THREE.Line) {

				_modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix);

				if (geometry instanceof THREE.BufferGeometry) {

					var attributes = geometry.attributes;

					if (attributes.position !== undefined) {

						var positions = attributes.position.array;

						for (var i = 0, l = positions.length; i < l; i += 3) {

							renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]);

						}

						if (attributes.color !== undefined) {

							var colors = attributes.color.array;

							for (var i = 0, l = colors.length; i < l; i += 3) {

								renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]);

							}

						}

						if (geometry.index !== null) {

							var indices = geometry.index.array;

							for (var i = 0, l = indices.length; i < l; i += 2) {

								renderList.pushLine(indices[i], indices[i + 1]);

							}

						} else {

							var step = object instanceof THREE.LineSegments ? 2 : 1;

							for (var i = 0, l = (positions.length / 3) - 1; i < l; i += step) {

								renderList.pushLine(i, i + 1);

							}

						}

					}

				} else if (geometry instanceof THREE.Geometry) {

					var vertices = object.geometry.vertices;

					if (vertices.length === 0) continue;

					v1 = getNextVertexInPool();
					v1.positionScreen.copy(vertices[0]).applyMatrix4(_modelViewProjectionMatrix);

					var step = object instanceof THREE.LineSegments ? 2 : 1;

					for (var v = 1, vl = vertices.length; v < vl; v++) {

						v1 = getNextVertexInPool();
						v1.positionScreen.copy(vertices[v]).applyMatrix4(_modelViewProjectionMatrix);

						if ((v + 1) % step > 0) continue;

						v2 = _vertexPool[_vertexCount - 2];

						_clippedVertex1PositionScreen.copy(v1.positionScreen);
						_clippedVertex2PositionScreen.copy(v2.positionScreen);

						if (clipLine(_clippedVertex1PositionScreen, _clippedVertex2PositionScreen) === true) {

							// Perform the perspective divide
							_clippedVertex1PositionScreen.multiplyScalar(1 / _clippedVertex1PositionScreen.w);
							_clippedVertex2PositionScreen.multiplyScalar(1 / _clippedVertex2PositionScreen.w);

							_line = getNextLineInPool();

							_line.id = object.id;
							_line.v1.positionScreen.copy(_clippedVertex1PositionScreen);
							_line.v2.positionScreen.copy(_clippedVertex2PositionScreen);

							_line.z = Math.max(_clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z);
							_line.renderOrder = object.renderOrder;

							_line.material = object.material;

							if (object.material.vertexColors === THREE.VertexColors) {

								_line.vertexColors[0].copy(object.geometry.colors[v]);
								_line.vertexColors[1].copy(object.geometry.colors[v - 1]);

							}

							_renderData.elements.push(_line);

						}

					}

				}

			} else if (object instanceof THREE.Points) {

				_modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix);

				if (geometry instanceof THREE.Geometry) {

					var vertices = object.geometry.vertices;

					for (var v = 0, vl = vertices.length; v < vl; v++) {

						var vertex = vertices[v];

						_vector4.set(vertex.x, vertex.y, vertex.z, 1);
						_vector4.applyMatrix4(_modelViewProjectionMatrix);

						pushPoint(_vector4, object, camera);

					}

				} else if (geometry instanceof THREE.BufferGeometry) {

					var attributes = geometry.attributes;

					if (attributes.position !== undefined) {

						var positions = attributes.position.array;

						for (var i = 0, l = positions.length; i < l; i += 3) {

							_vector4.set(positions[i], positions[i + 1], positions[i + 2], 1);
							_vector4.applyMatrix4(_modelViewProjectionMatrix);

							pushPoint(_vector4, object, camera);

						}

					}

				}

			} else if (object instanceof THREE.Sprite) {

				_vector4.set(_modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1);
				_vector4.applyMatrix4(_viewProjectionMatrix);

				pushPoint(_vector4, object, camera);

			}

		}

		if (sortElements === true) {

			_renderData.elements.sort(painterSort);

		}

		return _renderData;

	};

	function pushPoint(_vector4, object, camera) {

		var invW = 1 / _vector4.w;

		_vector4.z *= invW;

		if (_vector4.z >= - 1 && _vector4.z <= 1) {

			_sprite = getNextSpriteInPool();
			_sprite.id = object.id;
			_sprite.x = _vector4.x * invW;
			_sprite.y = _vector4.y * invW;
			_sprite.z = _vector4.z;
			_sprite.renderOrder = object.renderOrder;
			_sprite.object = object;

			_sprite.rotation = object.rotation;

			_sprite.scale.x = object.scale.x * Math.abs(_sprite.x - (_vector4.x + camera.projectionMatrix.elements[0]) / (_vector4.w + camera.projectionMatrix.elements[12]));
			_sprite.scale.y = object.scale.y * Math.abs(_sprite.y - (_vector4.y + camera.projectionMatrix.elements[5]) / (_vector4.w + camera.projectionMatrix.elements[13]));

			_sprite.material = object.material;

			_renderData.elements.push(_sprite);

		}

	}

	// Pools

	function getNextObjectInPool() {

		if (_objectCount === _objectPoolLength) {

			var object = new THREE.RenderableObject();
			_objectPool.push(object);
			_objectPoolLength++;
			_objectCount++;
			return object;

		}

		return _objectPool[_objectCount++];

	}

	function getNextVertexInPool() {

		if (_vertexCount === _vertexPoolLength) {

			var vertex = new THREE.RenderableVertex();
			_vertexPool.push(vertex);
			_vertexPoolLength++;
			_vertexCount++;
			return vertex;

		}

		return _vertexPool[_vertexCount++];

	}

	function getNextFaceInPool() {

		if (_faceCount === _facePoolLength) {

			var face = new THREE.RenderableFace();
			_facePool.push(face);
			_facePoolLength++;
			_faceCount++;
			return face;

		}

		return _facePool[_faceCount++];


	}

	function getNextLineInPool() {

		if (_lineCount === _linePoolLength) {

			var line = new THREE.RenderableLine();
			_linePool.push(line);
			_linePoolLength++;
			_lineCount++;
			return line;

		}

		return _linePool[_lineCount++];

	}

	function getNextSpriteInPool() {

		if (_spriteCount === _spritePoolLength) {

			var sprite = new THREE.RenderableSprite();
			_spritePool.push(sprite);
			_spritePoolLength++;
			_spriteCount++;
			return sprite;

		}

		return _spritePool[_spriteCount++];

	}

	//

	function painterSort(a, b) {

		if (a.renderOrder !== b.renderOrder) {

			return a.renderOrder - b.renderOrder;

		} else if (a.z !== b.z) {

			return b.z - a.z;

		} else if (a.id !== b.id) {

			return a.id - b.id;

		} else {

			return 0;

		}

	}

	function clipLine(s1, s2) {

		var alpha1 = 0, alpha2 = 1,

			// Calculate the boundary coordinate of each vertex for the near and far clip planes,
			// Z = -1 and Z = +1, respectively.

			bc1near = s1.z + s1.w,
			bc2near = s2.z + s2.w,
			bc1far = - s1.z + s1.w,
			bc2far = - s2.z + s2.w;

		if (bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0) {

			// Both vertices lie entirely within all clip planes.
			return true;

		} else if ((bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0)) {

			// Both vertices lie entirely outside one of the clip planes.
			return false;

		} else {

			// The line segment spans at least one clip plane.

			if (bc1near < 0) {

				// v1 lies outside the near plane, v2 inside
				alpha1 = Math.max(alpha1, bc1near / (bc1near - bc2near));

			} else if (bc2near < 0) {

				// v2 lies outside the near plane, v1 inside
				alpha2 = Math.min(alpha2, bc1near / (bc1near - bc2near));

			}

			if (bc1far < 0) {

				// v1 lies outside the far plane, v2 inside
				alpha1 = Math.max(alpha1, bc1far / (bc1far - bc2far));

			} else if (bc2far < 0) {

				// v2 lies outside the far plane, v2 inside
				alpha2 = Math.min(alpha2, bc1far / (bc1far - bc2far));

			}

			if (alpha2 < alpha1) {

				// The line segment spans two boundaries, but is outside both of them.
				// (This can't happen when we're only clipping against just near/far but good
				//  to leave the check here for future usage if other clip planes are added.)
				return false;

			} else {

				// Update the s1 and s2 vertices to match the clipped line segment.
				s1.lerp(s2, alpha1);
				s2.lerp(s1, 1 - alpha2);

				return true;

			}

		}

	}

};






if ($('#Wave2').length > 0) {
	var SEPARATION = 200, AMOUNTX = 40, AMOUNTY = 60;
	var container, stats;
	var camera, scene, renderer;

	var particles, particle, count = 0;

	var mouseX = 0, mouseY = 0;

	var windowHalfX = window.innerWidth / 2;
	var windowHalfY = window.innerHeight / 2;


	init();
	animate();

}


function init() {

	container = $('#Wave2');


	camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 1, 10000);
	camera.position.z = 10000;

	scene = new THREE.Scene();
	scene.fog = new THREE.Fog(0xffffff, 2000, 10000);

	particles = new Array();

	var PI2 = Math.PI * 2;
	var material = new THREE.SpriteCanvasMaterial({

		color: 0x696FC1,
		program: function (context) {

			context.beginPath();
			context.arc(0, 0, 0.5, 0, PI2, true);
			context.fill();

		}

	});

	var i = 0;

	for (var ix = 0; ix < AMOUNTX; ix++) {

		for (var iy = 0; iy < AMOUNTY; iy++) {

			particle = particles[i++] = new THREE.Sprite(material);
			particle.position.x = ix * SEPARATION - ((AMOUNTX * SEPARATION) / 2);
			particle.position.z = iy * SEPARATION - ((AMOUNTY * SEPARATION) / 2);
			scene.add(particle);

		}

	}

	renderer = new THREE.CanvasRenderer({ alpha: true });
	renderer.setPixelRatio(window.devicePixelRatio);
	// renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.setSize(window.innerWidth, container[0].offsetHeight);
	renderer.setClearColor(scene.fog.color);
	container.append(renderer.domElement);

	//stats = new Stats();
	//stats.domElement.style.position = 'absolute';
	//stats.domElement.style.top = '0px';
	//container.appendChild( stats.domElement );



	//

	window.addEventListener('resize', onWindowResize, false);

}

function onWindowResize() {

	windowHalfX = window.innerWidth / 2;
	windowHalfY = window.innerHeight / 2;

	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

	// renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.setSize(window.innerWidth, container[0].offsetHeight);

}

//

//

function animate() {

	requestAnimationFrame(animate);

	render();
	//stats.update();

}

function render() {


	camera.position.set(0, 355, 122);

	var i = 0;

	for (var ix = 0; ix < AMOUNTX; ix++) {

		for (var iy = 0; iy < AMOUNTY; iy++) {

			particle = particles[i++];
			particle.position.y = (Math.sin((ix + count) * 0.3) * 50) +
				(Math.sin((iy + count) * 0.5) * 50);
			particle.scale.x = particle.scale.y = (Math.sin((ix + count) * 0.3) + 1) * 4 +
				(Math.sin((iy + count) * 0.5) + 1) * 4;

		}

	}

	renderer.render(scene, camera);

	// 杩愬姩閫熷害
	count += 0.05;

}

